/**
 * @DESC
 * - Lambda<br />
 * - FunctionalInterface<br />
 * - Function reference<br />
 * - Default interface method<br />
 * - Static interface method<br />
 * - Optional<br />
 * - Streams<br />
 * - Date / time<br />
 */
package org.yuiwong.java8.playground;

import java.io.UnsupportedEncodingException;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.Period;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;

public class Java8NewFeaturesTester {
	@FunctionalInterface
	interface MathOperation {
		double operation(double a, double b);
	}

	private double calculate(double a, double b, MathOperation mathOper) {
		return mathOper.operation(a, b);
	}

	/**
	 * @NAME tryLambda - Try java8 new features lambda and related
	 * @DESC - Lambda<br />
	 *       - FunctionalInterface<br />
	 */
	private void tryLambda() {
		/* With type */

		double ret = this.calculate(100.0, 200.0, (double a, double b) -> a + b);
		System.out.println(ret);
		/* Without type */
		ret = this.calculate(100.0, 200.0, (a, b) -> a - b);
		System.out.println(ret);
		/* With { } and return */
		ret = this.calculate(100.0, 200.0, (double a, double b) -> {
			return a * b;
		});
		System.out.println(ret);
		/* Old */
		ret = this.calculate(100.0, 200.0, new MathOperation() {
			@Override
			public double operation(double a, double b) {
				return a / b;
			}
		});
		System.out.println(ret);
		/* New */
		ret = this.calculate(100.0, 200.0, (a, b) -> a / b);
		System.out.println(ret);
		/**
		 * Reference
		 */
		List<Double> list = new ArrayList<Double>();
		for (int i = 0; i < 5; ++i) {
			list.add(new Double((i + 1) * i));
		}
		list.forEach(System.out::println);
		try {
			while (!list.isEmpty()) {
				System.out.println("Remove: " + list.remove(0));
			}
		} catch (Exception e) {
			System.err.println("Exception: " + e.getMessage());
		}
	} /* tryLambda */

	public interface DefaultExample {
		default void description() {
			System.out.println("I am " + DefaultExample.class.getSimpleName());
		}
	}

	public interface DefaultExample2 {
		default void description() {
			System.out.println("I am " + DefaultExample2.class.getSimpleName());
		}
	}

	public interface StaticDefaultExample {
		static void description() {
			System.out.println("I am " + StaticDefaultExample.class.getSimpleName());
		}
	}

	public class DefaultImpl implements DefaultExample, DefaultExample2 {
		@Override
		public void description() {
			System.out.println("I am " + DefaultImpl.class.getSimpleName());
			DefaultExample.super.description();
			DefaultExample2.super.description();
		}
	}

	/**
	 * @NAME tryDefaultMethod - Try java8 new features default and related
	 * @DESC - Default interface method<br />
	 *       - Static interface method<br />
	 */
	private void tryDefaultMethod() {
		new DefaultImpl().description();
		StaticDefaultExample.description();
	} /* tryDefaultMethod */

	public double sum(Optional<Double> a, Optional<Double> b) {
		Double left = a.orElse(new Double(0.0));
		Double right = b.orElse(new Double(0.0));
		return left + right;
	} /* sum */

	private void tryOptional() {
		Optional<Double> l = Optional.ofNullable(null);
		Optional<Double> r = Optional.ofNullable(new Double(100.0));
		System.out.println(this.sum(l, r));
	} /* tryOptional */

	private void tryStreams() {
		/* forEach and limit */ {
			Random random = new Random();
			random.ints().limit(10).forEach(System.out::println);
		}
		/* map */ {
			List<Integer> numbers = Arrays.asList(1, 3, 5, 7);
			List<Integer> res = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
			res.forEach(System.out::println);
		}
		/* filter, parallelStream */ {
			List<String> ss = Arrays.asList("aaa", "bbb", "ccc", "", "eee");
			long count = ss.stream().filter(s -> s.isEmpty()).count();
			System.out.println("Count of empty: " + count);
			count = ss.parallelStream().filter(S -> S.isEmpty()).count();
			System.out.println("Count of empty: " + count);
		}
		/* sorted */ {
			Random random = new Random();
			random.ints().limit(10).sorted().forEach(System.out::println);
		}
		/* Collector */ {
			List<String> ss = Arrays.asList("aaa", "bbb", "ccc", "", "eee");
			List<String> filtered = ss.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
			System.out.println("Filtered: " + filtered);
			String mergedString = ss.stream().filter(s -> !s.isEmpty()).collect(Collectors.joining(", "));
			System.out.println("mergedString: " + mergedString);
		}
		/* Stats */ {
			List<Integer> numbers = Arrays.asList(1, 3, 5, 7);
			IntSummaryStatistics stats = numbers.stream().mapToInt(x -> x).summaryStatistics();
			System.out.println("Max: " + stats.getMax());
			System.out.println("Min: " + stats.getMin());
			System.out.println("Sum: " + stats.getSum());
			System.out.println("Average: " + stats.getAverage());
		}
	} /* tryStreams */

	private void tryDatetime() {
		/* Now */ {
			/* Current local date / time */
			LocalDateTime now = LocalDateTime.now();
			System.out.println("Now: " + now);
			/* Local date */
			System.out.println("Date: " + now.toLocalDate());
			/* Local time */
			System.out.println("Date: " + now.toLocalTime());
			/* yyyy MM dd hh mm ss */
			System.out.println("yyyy: " + now.getYear());
			System.out.println("MM: " + now.getMonthValue());
			System.out.println("dd: " + now.getDayOfMonth());
			System.out.println("hh: " + now.getHour());
			System.out.println("mm: " + now.getMinute());
			System.out.println("ss: " + now.getSecond());
			/* Current time with ... */
			System.out.println("With: " + now.withDayOfMonth(15).withMonth(12).withYear(1993));
			/* Unit */
			System.out.println("Datetime after 2 weeks: " + now.plus(2, ChronoUnit.WEEKS));
			System.out.println("Datetime after 6 months: " + now.plus(6, ChronoUnit.MONTHS));
			System.out.println("Datetime after 200 years: " + now.plus(20, ChronoUnit.DECADES));
			/* Period */
			LocalDate birthDate = LocalDate.parse("1990-10-19");
			Period ageDate = Period.between(now.toLocalDate(), birthDate);
			LocalTime birthTime = LocalTime.parse("01:30:15.123");
			Duration ageTime = Duration.between(now.toLocalTime(), birthTime);
			System.out.println("Age: " + ageDate + ", " + ageTime);
			/* Next Wed. date / time */
			System.out.println("Next Wed.: " + now.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY)));
			/* Next month, 3rd Sunday */ {
				LocalDateTime nextMonth = now.plus(1, ChronoUnit.MONTHS);
				System.out.println("Next month, 3rd Sunday: " + LocalDateTime
						.of(LocalDate.of(nextMonth.getYear(), nextMonth.getMonth(), 1), nextMonth.toLocalTime())
						.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY))
						.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)).with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));
				/* Way 2 */
				LocalDateTime nextMonthStart = LocalDateTime
						.of(LocalDate.of(nextMonth.getYear(), nextMonth.getMonth(), 1), nextMonth.toLocalTime());
				LocalDateTime soon = nextMonthStart.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
				LocalDateTime bingo = soon.plus(2, ChronoUnit.WEEKS);
				System.out.println("Next month, 3rd Sunday: " + bingo);
			}
		}
		/* Make date */
		System.out.println("Make date: " + LocalDate.of(2016, Month.DECEMBER, 25));
		/* Make time */
		System.out.println("Make time: " + LocalTime.of(19, 00, 01));
		/* Parse */
		System.out.println("Parse: " + LocalDateTime.parse("2016-12-01T00:30:15"));
		/* Tz */
		System.out.println("Current tz: " + ZoneId.systemDefault());
	} /* tryDatetime */

	private void tryBase64() {
		/* Base */
		String encoded;
		try {
			encoded = Base64.getEncoder().encodeToString("yuiwong.org".getBytes("utf-8"));
		} catch (UnsupportedEncodingException e) {
			encoded = "";
			e.printStackTrace();
		}
		System.out.println("Base64 encoded: " + encoded);
		byte[] decoded = Base64.getDecoder().decode(encoded);
		try {
			System.out.println("Base64 decoded: " + new String(decoded, "utf-8"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		/* Url */
		try {
			encoded = Base64.getUrlEncoder().encodeToString("yuiwong.org".getBytes("utf-8"));
		} catch (UnsupportedEncodingException e) {
			encoded = "";
			e.printStackTrace();
		}
		System.out.println("Base64 url encoded: " + encoded);
		decoded = Base64.getDecoder().decode(encoded);
		try {
			System.out.println("Base64 decoded: " + new String(decoded, "utf-8"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		/* Mime */
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < 10; ++i) {
			sb.append(UUID.randomUUID().toString());
		}
		byte[] mimeTypes;
		try {
			mimeTypes = sb.toString().getBytes("utf-8");
		} catch (UnsupportedEncodingException e) {
			mimeTypes = new byte[0];
			e.printStackTrace();
		}
		encoded = Base64.getMimeEncoder().encodeToString(mimeTypes);
		System.out.println("Base64 mime encoded: " + encoded);
	} /* tryBase64 */

	public static void main(String[] args) {
		Java8NewFeaturesTester tester = new Java8NewFeaturesTester();
		tester.tryLambda();
		tester.tryDefaultMethod();
		tester.tryOptional();
		tester.tryStreams();
		tester.tryDatetime();
		tester.tryBase64();
	}
}
